// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace osu.Framework.SourceGeneration.Generators
{
    public abstract class IncrementalSourceEmitter
    {
        protected abstract string FileSuffix { get; }

        private const string headers = @"// <auto-generated/>
#nullable enable
#pragma warning disable CS4014

";

        public readonly IncrementalSemanticTarget Target;

        protected IncrementalSourceEmitter(IncrementalSemanticTarget target)
        {
            Target = target;
        }

        public void Emit(AddSourceDelegate addSource)
        {
            if (!Target.IsValid)
                return;

            StringBuilder result = new StringBuilder();
            result.Append(headers);

            if (Target.ContainingNamespace == null)
            {
                result.Append(
                    emitTypeTree().NormalizeWhitespace());
            }
            else
            {
                result.Append(
                    SyntaxFactory.NamespaceDeclaration(
                                     SyntaxFactory.IdentifierName(Target.ContainingNamespace))
                                 .WithMembers(
                                     SyntaxFactory.SingletonList(
                                         emitTypeTree()))
                                 .NormalizeWhitespace());
            }

            // Fully qualified name, with generics replaced with friendly characters.
            string typeName = Target.FullyQualifiedTypeName.Replace('<', '{').Replace('>', '}');
            string filename = $"g_{typeName}_{FileSuffix}.cs";

            addSource(filename, result.ToString());
        }

        protected abstract ClassDeclarationSyntax ConstructClass(ClassDeclarationSyntax initialClass);

        private MemberDeclarationSyntax emitTypeTree()
        {
            List<ClassDeclarationSyntax> classes = new List<ClassDeclarationSyntax>();

            foreach (string type in Target.TypeHierarchy)
                classes.Add(createClassSyntax(type));

            classes[0] = ConstructClass(classes[0]);

            for (int i = 0; i < classes.Count - 1; i++)
                classes[i + 1] = classes[i + 1].WithMembers(SyntaxFactory.List<MemberDeclarationSyntax>(new[] { classes[i] }));

            return classes.Last();

            static ClassDeclarationSyntax createClassSyntax(string type) =>
                SyntaxFactory.ClassDeclaration(type)
                             .WithModifiers(
                                 SyntaxTokenList.Create(
                                     SyntaxFactory.Token(SyntaxKind.PartialKeyword)));
        }
    }

    public delegate void AddSourceDelegate(string filename, string sourceText);
}
